/*
 * $Id: QEContact.m 248 2008-09-08 15:31:38Z jon $
 * Sleeper
 *
 * Created by Jon Gordon on 7/28/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#import "QEContact.h"

#import <objc/objc-runtime.h>

#import "QEArrlData.h"

#import "QECallsign.h"
#import "QEExchangeElement.h"
#import "QEExchangeResolver.h"

static QEExchangeElement *callElement;
static QEExchangeElement *checkElement;
static QEExchangeElement *freqElement;
static QEExchangeElement *precElement;
static QEExchangeElement *sectionElement;
static QEExchangeElement *serialRcvdElement;

static NSCharacterSet *precedenceSet;


@interface QEContact (private)
- (id)extension;					// field reserved for future extension
- (void)setExtension:(id)newValue;
@end


//
// Represents a single contact in the contest log.
//
// Author: $Author: jon@bway.net $
// Version $Revision: 2 $
//
@implementation QEContact
+ (void)initialize
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        NSLog(@"initializing QEContact");
        
        callElement = [QEExchangeElement elementForName:@"call"];
        NSAssert((nil != callElement), @"nil element returned for call element");
        checkElement = [QEExchangeElement elementForName:@"check"];
        NSAssert((nil != checkElement), @"nil element returned for check element");
        freqElement = [QEExchangeElement elementForName:@"freq"];
        NSAssert((nil != freqElement), @"nil element returned for freq element");
        precElement = [QEExchangeElement elementForName:@"prec"];
        NSAssert((nil != precElement), @"nil element returned for prec element");
        sectionElement = [QEExchangeElement elementForName:@"section"];
        NSAssert((nil != sectionElement), @"nil element returned for section element");
        serialRcvdElement = [QEExchangeElement elementForName:@"serialRcvd"];
        NSAssert((nil != serialRcvdElement), @"nil element returned for serialRcvd element");
        
        precedenceSet = [[NSCharacterSet characterSetWithCharactersInString:@"QABUMS"] retain];
    } // if
} // initialize


- (id)init {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [self initWithSerialSent:nil
                               time:nil
                               freq:nil
                               call:nil
                         serialRcvd:nil
                               prec:nil
                              check:nil
                            section:nil];
} // init


- (id)initWithSerialSent:(NSNumber *)newSerialSent
                    time:(NSDate *)newTime
                    freq:(NSNumber *)newFreq
                    call:(NSString *)newCall
              serialRcvd:(NSNumber *)newSerialRcvd
                    prec:(NSString *)newPrec
                   check:(NSNumber *)newCheck
                 section:(NSString *)newSection {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    if (self != nil) {
        [self setSerialSent:newSerialSent];
        [self setTime:newTime];
        [self setFreq:newFreq];
        [self setCall:newCall];
        [self setSerialRcvd:newSerialRcvd];
        [self setPrec:newPrec];
        [self setCheck:newCheck];
        [self setSection:newSection];
    } // if
    return self;
} // initWithSerialSent:time:frequency:callsign:serialRcvd:precedence:check:section:


// Supports Cocoa's serialize/deserialize function
- (id)initWithCoder:(NSCoder *)coder
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    
    [self setSerialSent:[coder decodeObjectForKey:@"CoSerialSent"]];
    [self setTime:[coder decodeObjectForKey:@"CoTime"]];
    [self setFreq:[coder decodeObjectForKey:@"CoFreq"]];
    [self setCall:[coder decodeObjectForKey:@"CoCall"]];
    [self setSerialRcvd:[coder decodeObjectForKey:@"CoSerialRcvd"]];
    [self setPrec:[coder decodeObjectForKey:@"CoPrec"]];
    [self setCheck:[coder decodeObjectForKey:@"CoCheck"]];
    [self setSection:[coder decodeObjectForKey:@"CoSection"]];
    
    [self setExtension:[coder decodeObjectForKey:@"CoExtension1"]];
    
    return self;
} // initWithCoder:


// Supports Cocoa's serialize/deserialize function
- (void)encodeWithCoder:(NSCoder *)coder {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [coder encodeObject:[self serialSent] forKey:@"CoSerialSent"];
    [coder encodeObject:[self time] forKey:@"CoTime"];
    [coder encodeObject:[self freq] forKey:@"CoFreq"];
    [coder encodeObject:[self call] forKey:@"CoCall"];
    [coder encodeObject:[self serialRcvd] forKey:@"CoSerialRcvd"];
    [coder encodeObject:[self prec] forKey:@"CoPrec"];
    [coder encodeObject:[self check] forKey:@"CoCheck"];
    [coder encodeObject:[self section] forKey:@"CoSection"];
} // encodeWithCoder:


- (NSNumber *)serialSent
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[serialSent retain] autorelease];
} // serialSent


- (void)setSerialSent:(NSNumber *)newSerialSent
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newSerialSent != serialSent) {
        [serialSent autorelease];
        serialSent = [newSerialSent copy];
    } // if
} // setSerialSent:


- (BOOL)validateSerialSent:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == *ioValue)
        return YES;
    
    NSNumber *newObject = nil;
    if ([*ioValue isKindOfClass:[NSNumber class]]) {
        newObject = *ioValue;
    } else if ([*ioValue respondsToSelector:@selector(integerValue)]) {
        int (*getIntegerValue)(id, SEL) = ((int (*)(id, SEL)) objc_msgSend);
        newObject = [NSNumber numberWithInt:(*getIntegerValue)(*ioValue, 
                                                               @selector(integerValue))];
    } else if ([*ioValue respondsToSelector:@selector(intValue)]) {
        newObject = [NSNumber numberWithInt:[*ioValue intValue]];
    } // else if
    
    if ((nil != newObject) && (0 < [newObject intValue])) {
        *ioValue = newObject;
        return YES;
    } // if
    
    return NO;
} // validateSerialSent:error:


- (NSNumber *)freq
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[freq retain] autorelease];
}


- (void)setFreq:(NSNumber *)newFrequency
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newFrequency != freq) {
        [freq autorelease];
        freq = [newFrequency copy];
    } // if
} // setFrequency:


- (BOOL)validateFreq:(id *)ioValue error:(NSError **)outError
{
    /*
     * For this purpose, a value is considered a valid frequency if
     * it can be transformed into a floating-point number that is
     * greater than zero.
     */
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == *ioValue)
        return YES;
    
    NSNumber *newObject = nil;
    if ([*ioValue isKindOfClass:[NSNumber class]]) {
        newObject = *ioValue;
    } else if ([*ioValue respondsToSelector:@selector(floatValue)]) {
        newObject = [NSNumber numberWithFloat:[*ioValue floatValue]];
    } // else if
    
    if ((nil != newObject) && (0.0 < [newObject floatValue])) {
        *ioValue = newObject;
        return YES;
    } // if
    
    return NO;
} // validateFrequency:error:


- (NSString *)call
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[call retain] autorelease];
} // callsign


- (void)setCall:(NSString *)newCallsign
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newCallsign != call) {
        [call autorelease];
        call = [newCallsign copy];
    } // if
} // setCallsign:


- (BOOL)validateCall:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [QECallsign isValidCallsign:ioValue error:outError];
} // validateCallsign:error:


- (NSNumber *)serialRcvd
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[serialRcvd retain] autorelease];
} // serialRcvd


- (void)setSerialRcvd:(NSNumber *)newSerialRcvd
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newSerialRcvd != serialRcvd) {
        [serialRcvd autorelease];
        serialRcvd = [newSerialRcvd copy];
    } // if
} // setSerialRcvd:


- (BOOL)validateSerialRcvd:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (nil == *ioValue)
        return YES;
    
    NSNumber *newObject = nil;
    if ([*ioValue isKindOfClass:[NSNumber class]]) {
        newObject = *ioValue;
    } else if ([*ioValue respondsToSelector:@selector(integerValue)]) {
        int (*getIntegerValue)(id, SEL) = ((int (*)(id, SEL)) objc_msgSend);
        newObject = [NSNumber numberWithInt:(*getIntegerValue)(*ioValue, 
                                                               @selector(integerValue))];
    } else if ([*ioValue respondsToSelector:@selector(intValue)]) {
        newObject = [NSNumber numberWithInt:[*ioValue intValue]];
    } // else if
    
    if ((nil != newObject) && (0 < [newObject intValue])) {
        *ioValue = newObject;
        return YES;
    } // if
    
    return NO;
} // validateSerial:error:


- (NSString *)prec
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[prec retain] autorelease];
} // precedence


- (void)setPrec:(NSString *)newPrecedence
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newPrecedence != prec) {
        [prec autorelease];
        prec = [newPrecedence copy];
    } // if
} // setPrecedence:


- (BOOL)validatePrec:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    
    // precedence property is allowed to be blank
    if ((nil == *ioValue) || (0 == [*ioValue length])) {
        return YES;
    } // if
    
    if (1 == [*ioValue length]) {
        if ([precedenceSet characterIsMember:[*ioValue characterAtIndex:0]]) {
            return YES;
        } else {
            NSString *newValue = [*ioValue uppercaseString];
            if ([precedenceSet characterIsMember:[newValue characterAtIndex:0]]) {
                *ioValue = newValue;
                return YES;
            } // if
        } // else
    } // if
    
    // TODO: return error information
    return NO;
} // validatePrecedence:


- (NSNumber *)check
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[check retain] autorelease];
} // check


- (void)setCheck:(NSNumber *)newCheck
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newCheck != check) {
        [check autorelease];
        check = [newCheck copy];
    } // if
} // setCheck:


- (BOOL)validateCheck:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // valid check is an integer in the range 0 to 99, inclusive
    if (nil == *ioValue)
        return YES;
    
    int value = [*ioValue intValue];
    NSLog(@"    translated NSNumber with value %@ to integer with value %d", *ioValue, value);
    // rule out floats and the like that might not exactly equal integer values
    if (([[NSNumber numberWithInt:value] isEqual:*ioValue]) &&
        (0 <= value) && (value <= 99)) {
        return YES;
    } // if
    
    return NO;
} // validateCheck:error:


- (NSString *)section
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[section retain] autorelease];
} // section


- (void)setSection:(NSString *)newSection
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newSection != section) {
        [section autorelease];
        section = [newSection copy];
    } // if
} // setSection:


- (BOOL)validateSection:(id *)ioValue error:(NSError **)outError
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    // TODO: implement validation
    // section is allowed to be nil or blank
    if ((nil == *ioValue) || (0 == [*ioValue length])) {
        return YES;
    } // if
    
    // valid sections are all uppercase, but we can fix lowercase values
    NSString *testValue = [*ioValue uppercaseString];
    if ([testValue isEqual:*ioValue]) {
        testValue = *ioValue; // make sure uppercase ioValue isn't replaced
    } // if
    
    if ([QEArrlData isSectionAbbr:testValue]) {
        *ioValue = testValue;
        return YES;
    } // if
    
    return NO;
} // validateSection:error:


- (NSDate *)time
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[time retain] autorelease];
} // time


- (void)setTime:(NSDate *)newTime
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (newTime != time) {
        [time autorelease];
        time = [newTime copy];
    } // if
} // setTime:


- (id)copyWithZone:(NSZone *)zone
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    QEContact *copy = [[[self class] allocWithZone: zone]
                       initWithSerialSent:[self serialSent]
                       time:[self time]
                       freq:[self freq]
                       call:[self call]
                       serialRcvd:[self serialRcvd]
                       prec:[self prec]
                       check:[self check]
                       section:[self section]];
    
    return copy;
} // copyWithZone:


- (void) dealloc {
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [call release];
    [check release];
    [serialSent release];
    [freq release];
    [prec release];
    [section release];
    [serialRcvd release];
    [time release];
    
    [super dealloc];
} // dealloc


- (unsigned int)hash
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static const unsigned int HASH_BASE = 17;
    static const unsigned int HASH_FACTOR = 37;
    
    unsigned int result = HASH_BASE;
    result = (result * HASH_FACTOR) + [[self call] hash];
    result = (result * HASH_FACTOR) + [[self check] intValue];
    result = (result * HASH_FACTOR) + [[self serialSent] intValue];
    result = (result * HASH_FACTOR) + [[self freq] intValue];
    result = (result * HASH_FACTOR) + [[self prec] hash];
    result = (result * HASH_FACTOR) + [[self section] hash];
    result = (result * HASH_FACTOR) + [[self serialRcvd] intValue];
    result = (result * HASH_FACTOR) + [[self time] hash];
    
    return result;
} // hash

- (BOOL)isEqual:(id)anObject
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return ([[self call] isEqual:[anObject call]] &&
            [[self check] isEqual:[anObject check]] &&
            [[self serialRcvd] isEqual:[anObject serialRcvd]] &&
            [[self freq] isEqual:[anObject freq]] &&
            [[self prec] isEqual:[anObject prec]] &&
            [[self section] isEqual:[anObject section]] &&
            [[self serialSent] isEqual:[anObject serialSent]] &&
            [[self time] isEqual:[anObject time]]);
} // isEqual:
@end


@implementation QEContact (private)
- (id)extension
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
	return [[extension retain] autorelease];
} // extension

- (void)setExtension:(id)newValue
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
	if (extension != newValue) {
		id tmp = extension;
		extension = [newValue copy];
		[tmp release];
	} // if
} // setExtension:
@end